package com.huike.pmps.config.oauth;

import com.alibaba.fastjson.JSON;
import com.huike.pmps.model.vo.Result;
import com.huike.pmps.model.vo.StatusCode;
import com.huike.pmps.service.impl.security.UserDetailServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Base64;

/**
 * 〈OAuth2认证服务器〉
 */
@Configuration
@EnableAuthorizationServer
@Slf4j
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private UserDetailServiceImpl userDetailService;
    @Autowired
    private ClientDetailsService clientDetailsService;


    /**
     * 密码模式获取token失败——参数中userName和password不正确，异常解析
     *
     * @return
     */
    public WebResponseExceptionTranslator<OAuth2Exception> oauth2ResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {
            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
                OAuth2Exception body = OAuth2Exception.create(OAuth2Exception.ACCESS_DENIED, e.getMessage());
                // 捕获后在返回值添加flag,code、message,data
                body.addAdditionalInformation("flag", "false");
                body.addAdditionalInformation("code", String.valueOf(StatusCode.FAIL_401));
                body.addAdditionalInformation("data", null);
                body.addAdditionalInformation("message", "用户名或密码错误");
                if(body.getMessage().contains("locked")){
                    body.addAdditionalInformation("message", "账号处于锁定状态,请联系管理员进行启用");
                }
                HttpHeaders headers = new HttpHeaders();
                return new ResponseEntity<>(body, headers, HttpStatus.UNAUTHORIZED);
            }
        };
    }


    /**
     * 客户端client_id、client_secret认证
     * @return
     */
    public OncePerRequestFilter oncePerRequestFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                if (!request.getRequestURI().contains("/oauth/token")) {
                    filterChain.doFilter(request, response);
                    return;
                }

                String[] clientDetails = this.isHasClientDetails(request);
                //客户端信息缺失
                if (clientDetails == null) {
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    response.getWriter().write(JSON.toJSONString(Result.failure(StatusCode.FAIL_401, "客户端认证失败:缺少client_id、client_secret认证信息")));
                    return;
                }
                try {
                    this.handle(request, response, clientDetails, filterChain);
                } catch (Exception coe) {
                    // 客户端认证失败
                    response.setCharacterEncoding("UTF-8");
                    response.setContentType("application/json; charset=utf-8");
                    response.getWriter().write(JSON.toJSONString(Result.failure(StatusCode.FAIL_401, "客户端认证失败:client_id 或client_secret 不正确")));
                }

            }

            private void handle(HttpServletRequest request, HttpServletResponse response, String[] clientDetails, FilterChain filterChain) throws Exception {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (authentication != null && authentication.isAuthenticated()) {
                    filterChain.doFilter(request, response);
                    return;
                }

                ClientDetails details = null;
                try {
                    details =clientDetailsService.loadClientByClientId(clientDetails[0]);
                } catch (ClientRegistrationException e) {
                    log.info("client认证失败，{},{}", e.getMessage(), clientDetails[0]);
                    throw new Exception("client_id 或client_secret 不正确");
                }

                if (details == null) {
                    log.info("client认证失败，{}", clientDetails[0]);
                    throw new Exception("client_id或client_secret不正确");
                }

                UsernamePasswordAuthenticationToken token =
                        new UsernamePasswordAuthenticationToken(details.getClientId(), details.getClientSecret(), details.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(token);
                filterChain.doFilter(request, response);
            }


            /**
             * 判断请求头中是否包含client信息，不包含返回null  Base64编码
             */
            private String[] isHasClientDetails(HttpServletRequest request) {
                String[] params = null;
                String header = request.getHeader(HttpHeaders.AUTHORIZATION);
                if (header != null) {
                    String basic = header.substring(0, 5);
                    if (basic.toLowerCase().contains("basic")) {
                        String tmp = header.substring(6);
                        String defaultClientDetails = new String(Base64.getDecoder().decode(tmp));
                        String[] clientArrays = defaultClientDetails.split(":");

                        if (clientArrays.length != 2) {
                            return params;
                        } else {
                            params = clientArrays;
                        }
                    }
                }
                String id = request.getParameter("client_id");
                String secret = request.getParameter("client_secret");
                if (header == null && id != null) {
                    params = new String[]{id, secret};
                }
                return params;
            }
        };
    }

    /**
     * 这个是定义授权的请求的路径的Bean
     *
     * @return
     */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }


    /**
     * ClientDetailsServiceConfigurer：用来配置客户端详情服务（ClientDetailsService），
     * 客户端详情信息在这里进行初始化，你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails());
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                //将token存储到redis中
                .tokenStore(new RedisTokenStore(redisConnectionFactory))
                .authenticationManager(authenticationManager)
                //必须要注入 userDetailService，否则根据refresh_token无法加载用户信息，因为我们只传递了client的信息。
                .userDetailsService(userDetailService)
                //允许 GET、POST 请求获取 token，即访问端点：oauth/token
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

        //密码模式获取token失败——参数中userName和password不正确，异常解析
        endpoints.exceptionTranslator(oauth2ResponseExceptionTranslator());
    }


    /**
     * 用来配置令牌端点(Token Endpoint)的安全约束.
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                //允许表单认证
                .allowFormAuthenticationForClients()
                //开启/oauth/token_key验证端口无权限访问
                .tokenKeyAccess("permitAll()")
                //开启/oauth/check_token验证端口认证权限访问
                .checkTokenAccess("isAuthenticated()");

        //客户端/密码模式获取token失败——参数中未携带client_id、参数中client_id或client_secret不正确
        security.addTokenEndpointAuthenticationFilter(oncePerRequestFilter());

    }


}
